<!-- 
  虚拟长列表的实现原理：
    1. 根据可视区域的高度来计算出能够渲染的数据条数
    2. 从原始数据中做裁切出用于渲染到屏幕上的条数
    3. 在滚动处理函数中，根据滚动的距离，从新计算出要裁切的数据
    4. 起点就是当前滚出去的数据条数
    5. 终点就是起点 + 要渲染的数据条数
    6. 每个列表项，设置定位 + 平移，让其一直在可视区域
 -->
<template>
  <div ref="containerRef" class="recycle-scroller" @scroll="onScroll">
    <div class="wrapper" :style="{ height: height + 'px' }">
      <div
        class="recycle-scroller__item"
        v-for="(item, index) in list"
        :key="item[keyField]"
        :title="item[keyField]"
        :style="{ transform: `translateY(${(index + startIndex) * itemSize}px)` }"
      >
        <slot :item="item" />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'

defineOptions({
  name: 'RecycleScroller'
})

const props = defineProps<{
  /**
   * 列表数据
   */
  items: any[]
  /**
   * 列表项的高度
   */
  itemSize: number
  /**
   * 用作 key 的字段
   */
  keyField: string
}>()

// 容器的Ref
const containerRef = ref<HTMLDivElement | null>(null)

// wrapper 高度，用于撑起容器，让其出现滚动条
const height = computed(() => props.items.length * props.itemSize)

// 开始下标
const startIndex = ref(0)
// 结束下标
const endIndex = ref(0)
// 一个可视区域内可以渲染的数据条数
const count = ref(0)

// 用于渲染的 列表数据
const list = computed(() => props.items.slice(startIndex.value, endIndex.value))

// 容器滚动
function onScroll(event: Event) {
  // 1. 获取滚动条滚动的距离
  const scrollTop = (event.target as HTMLDivElement).scrollTop
  console.log('scrollTop', scrollTop)
  // 2. 基于 scrollTop 与 props.itemSize 计算 startIndex
  startIndex.value = Math.floor(scrollTop / props.itemSize)
  endIndex.value = startIndex.value + count.value
}

onMounted(() => {
  if (!containerRef.value) return

  // 1. 获取容器的可视区域高度
  const clientHeight = containerRef.value.clientHeight
  console.log('clientHeight', clientHeight)
  // 2. 基于 clientHeight 与 props.itemSize 算出一个可视区域内可以渲染多少条数据
  const num = Math.ceil(clientHeight / props.itemSize) + 2
  console.log('num', num)
  // 3. 使用 num 赋值 count 与 endIndex
  count.value = num
  endIndex.value = num
})
</script>

<style lang="scss" scoped>
.recycle-scroller {
  position: relative;

  &__item {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
  }
}
</style>
